local _, playerClass = UnitClass("player")
if playerClass ~= "DRUID" and playerClass ~="MAGE" and playerClass ~="PALADIN" and playerClass ~="PRIEST" and playerClass ~="SHAMAN" and playerClass ~="WARLOCK" and playerClass ~= "DEATHKNIGHT" then return end
local playerHealer = (playerClass == "PRIEST") or (playerClass == "SHAMAN") or (playerClass == "PALADIN") or (playerClass == "DRUID")
local playerHybrid = (playerClass == "DRUID") or (playerClass == "PALADIN") or (playerClass == "SHAMAN") or (playerClass == "DEATHKNIGHT")

--Libraries
DrDamage = AceLibrary:HasInstance("FuBarPlugin-2.0") and AceLibrary("AceAddon-2.0"):new("AceConsole-2.0", "AceEvent-2.0", "AceDB-2.0", "AceHook-2.1", "FuBarPlugin-2.0") or AceLibrary("AceAddon-2.0"):new("AceConsole-2.0", "AceEvent-2.0", "AceDB-2.0", "AceHook-2.1")
local L = AceLibrary("AceLocale-2.2"):new("DrDamage")
local GT = LibStub:GetLibrary("LibGratuity-3.0")
local DrDamage = DrDamage

--General
local settings
local type = type
local pairs = pairs
local ipairs = ipairs
local tonumber = tonumber
local next = next
local math_abs = math.abs
local math_floor = math.floor
local math_ceil = math.ceil
local math_min = math.min
local math_max = math.max
local string_match = string.match
local string_gsub = string.gsub
local string_find = string.find
local select = select

--Module
local UnitDamage = UnitDamage
local UnitBuff = UnitBuff
local UnitDebuff = UnitDebuff
local UnitLevel = UnitLevel
local UnitPower = UnitPower
local UnitPowerType = UnitPowerType
local UnitIsPlayer = UnitIsPlayer
local UnitIsFriend = UnitIsFriend
local UnitExists = UnitExists
local UnitStat = UnitStat
local UnitRangedDamage = UnitRangedDamage
local GetSpellInfo = GetSpellInfo
local GetSpellBonusDamage = GetSpellBonusDamage
local GetSpellBonusHealing = GetSpellBonusHealing
local GetSpellCritChance = GetSpellCritChance
local GetCritChance = GetCritChance
local GetCombatRating = GetCombatRating
local GetCombatRatingBonus = GetCombatRatingBonus
local GetRangedCritChance = GetRangedCritChance
local GetManaRegen = GetManaRegen
local GetTalentInfo = GetTalentInfo
local HasWandEquipped = HasWandEquipped
local IsEquippedItem = IsEquippedItem
local IsShiftKeyDown = IsShiftKeyDown

--Module variables
local DrD_ClearTable, DrD_Round, DrD_MatchData, DrD_DmgCalc, DrD_BuffCalc
local spellInfo, PlayerAura, TargetAura, Consumables, Calculation, SetBonuses

local function DrD_Set( n, setOnly )
	return function(v) 
		settings[n] = v
		if not setOnly and not DrDamage:IsEventScheduled("DrD_FullUpdate") then
			DrDamage:ScheduleEvent("DrD_FullUpdate", DrDamage.UpdateAB, 0.25, DrDamage)
		end
	end
end

DrDamage.defaults = {
	--Calculation
	HitCalc = false,
	HitTarget = false,
	TargetLevel = 3,
	TargetPlayer = false,
	SpellDamage = 0,
	HitRating = 0,
	CritRating = 0,
	HasteRating = 0,
	MP5 = 0,
	Resilience = 0,
	Custom = false,
	CustomAdd = true,
	ManaConsumables = false,
	--Actionbar:
	DisplayType = "AvgTotal",
	CastsLeft = false,
	CastsLeftDmg = false,
	--Tooltip:
	PlusDmg = false,
	Coeffs = true,
	DispCrit = true,
	DispHit = true,
	AvgHit = true,
	AvgCrit = true,
	Ticks = true,
	Total = true,
	Extra = true,
	Next = true,
	DPS = true,
	DPM = true,
	Doom = true,
	Casts = true,
	TwoRoll = false,
	ManaUsage = false,
	--Comparisons
	CompareDmg = true,
	CompareHit = false,
	CompareCrit = false,
	CompareHaste = false,
}

function DrDamage:Caster_Options()
	local table
	
	if playerHybrid then
		self.options.args.Caster = { type = 'group', name = L["Caster"], desc = L["Caster options."], order = 2, args = {} }
		table = self.options.args.Caster.args	
	else
		table = self.options.args
	end	
	
	table.ActionBar = {
		type = 'group', name = L["Actionbar"], desc = L["Actionbar options."],
		args = {
			DisplayType = {
				type = 'text',
				name = L["Display"],
				desc = L["Choose what to display on the actionbar."],
				validate =  {
					["Avg"] = L["Avg"],
					["AvgTotal"] = L["AvgTotal"],
					["AvgHit"] = L["AvgHit"],
					["AvgHitTotal"] = L["AvgHitTotal"], 
					["MinHit"] = L["MinHit"], 
					["MaxHit"] = L["MaxHit"], 
					["Max"] = L["Max"], 
					["DPS"] = L["DPS"], 
					["DPSC"] = L["DPSC"], 
					["DPSCD"] = L["DPSCD"], 
					["DPM"] = L["DPM"], 
					["MPS"] = L["MPS"], 
					["ManaCost"] = L["ManaCost"], 
					["TrueManaCost"] = L["TrueManaCost"],
				},
				get =  function() return settings["DisplayType"] end,
				set =  DrD_Set("DisplayType"),
				order = 70,
			}, 					
			CastsLeftDmg = {
				type = 'toggle',
				name = L["Damage spell casts"],
				desc = L["Actionbar displays the amount of damage spell casts left with current mana pool."],
				order = 100,
				get = function() return settings["CastsLeftDmg"] end,
				set = function(v)
					settings.CastsLeftDmg = v
					if v then
						if not self:IsBucketEventRegistered("UNIT_MANA") then
							self:RegisterBucketEvent("UNIT_MANA", 2)
						end
					else
						if self:IsBucketEventRegistered("UNIT_MANA") and not settings.CastsLeft then
							self:UnregisterBucketEvent("UNIT_MANA")
						end
					end
					self:CancelSpellUpdate()
					self:UpdateAB()
				end,
			},				
		},
	}
	table.Tooltip = {
		type = 'group', name = L["Tooltip"], desc = L["Tooltip options."], 
		args = {					
			PlusDmg = {
				type = 'toggle',
				name = L["Show efficient spellpower"],
				desc = L["Show efficient spellpower"],
				order = 52,
				get = function() return settings["PlusDmg"] end,
				set = DrD_Set("PlusDmg", true),
			},
			Coeffs = {
				type = 'toggle',
				name = L["Show coefficients"],
				desc = L["Show coefficients"],
				order = 53,
				get = function() return settings["Coeffs"] end,
				set = DrD_Set("Coeffs", true),
			},				
			DispCrit = {
				type = 'toggle',
				name = L["Show crit %"],
				desc = L["Show crit %"],
				order = 54,
				get = function() return settings["DispCrit"] end,
				set = DrD_Set("DispCrit", true),
			},
			DispHit = {
				type = 'toggle',
				name = L["Show hit %"],
				desc = L["Show hit %"],
				order = 56,
				get = function() return settings["DispHit"] end,
				set = DrD_Set("DispHit", true),
			},				
			AvgHit = {
				type = 'toggle',
				name = L["Show avg and hit range"],
				desc = L["Show avg and hit range"],
				order = 58,
				get = function() return settings["AvgHit"] end,
				set = DrD_Set("AvgHit", true),
			},
			AvgCrit = {
				type = 'toggle',
				name = L["Show avg crit and crit range"],
				desc = L["Show avg crit and crit range"],
				order = 60,
				get = function() return settings["AvgCrit"] end,
				set = DrD_Set("AvgCrit", true),
			},
			Ticks = {
				type = 'toggle',
				name = L["Show per tick"],
				desc = L["Show per tick"],
				order = 62,
				get = function() return settings["Ticks"] end,
				set = DrD_Set("Ticks", true),
			},
			Total = {
				type = 'toggle',
				name = L["Show avg total"],
				desc = L["Show avg total"],
				order = 64,
				get = function() return settings["Total"] end,
				set = DrD_Set("Total", true),
			},				
			Extra = {
				type = 'toggle',
				name = L["Show additional effects"],
				desc = L["Show additional effects"],
				order = 66,
				get = function() return settings["Extra"] end,
				set = DrD_Set("Extra", true),
			},
			Next = {
				type = 'toggle',
				name = L["Show stat increase values"],
				desc = L["Show stat increase values"],
				order = 68,
				get = function() return settings["Next"] end,
				set = DrD_Set("Next", true),
			},
			CompareDmg = {
				type = 'toggle',
				name = L["Compare spell power"],
				desc = L["Compare spell power"],
				order = 69,
				get = function() return settings["CompareDmg"] end,
				set = DrD_Set("CompareDmg", true),
			},
			CompareCrit = {
				type = 'toggle',
				name = L["Compare critical strike rating"],
				desc = L["Compare critical strike rating"],
				order = 70,
				get = function() return settings["CompareCrit"] end,
				set = DrD_Set("CompareCrit", true),
			},
			CompareHit = {
				type = 'toggle',
				name = L["Compare hit rating"],
				desc = L["Compare hit rating"],
				order = 71,
				get = function() return settings["CompareHit"] end,
				set = DrD_Set("CompareHit", true),
			},			
			CompareHaste = {
				type = 'toggle',
				name = L["Compare haste rating"],
				desc = L["Compare haste rating"],
				order = 72,
				get = function() return settings["CompareHaste"] end,
				set = DrD_Set("CompareHaste", true),
			},
			DPS = {
				type = 'toggle',
				name = L["Show DPS/HPS"],
				desc = L["Show DPS/HPS"],
				order = 80,
				get = function() return settings["DPS"] end,
				set = DrD_Set("DPS", true),
			},
			DPM = {
				type = 'toggle',
				name = L["Show DPM/HPM"],
				desc = L["Show DPM/HPM"],
				order = 85,
				get = function() return settings["DPM"] end,
				set = DrD_Set("DPM", true),
			},
			Doom = {
				type = 'toggle',
				name = L["Show damage/healing until OOM"],
				desc = L["Show damage/healing until OOM"],
				order = 90,
				get = function() return settings["Doom"] end,
				set = DrD_Set("Doom", true),
			},
			Casts = {
				type = 'toggle',
				name = L["Show total casts and time until OOM"],
				desc = L["Show total casts and time until OOM"],
				order = 95,
				get = function() return settings["Casts"] end,
				set = DrD_Set("Casts", true),
			},
			ManaUsage = {
				type = 'toggle',
				name = L["Show additional mana usage information"],
				desc = L["Show additional mana usage information"],
				order = 100,
				get = function() return settings["ManaUsage"] end,
				set = DrD_Set("ManaUsage", true),
			},
		},
	}		
	table.Calculation = {
		type = 'group', name = L["Calculation"], desc = L["Calculation options."],
		args = {							
			HitCalc = {			
				type = 'toggle',
				name = L["Hit calculation"],
				desc = L["Hit calculation into averages."],
				order = 65,
				get = function() return settings["HitCalc"] end,
				set = DrD_Set("HitCalc"),
			},
			TwoRoll = {
				type = 'toggle',
				name = L["Two roll calculation"],
				desc = L["Uses a two roll calculation for spells."],
				order = 70,
				get = function() return settings["TwoRoll"] end,
				set = DrD_Set("TwoRoll"),
			},			
			HitTarget = {			
				type = 'toggle',
				name = L["Calculate by current target"],
				desc = L["Calculates by current target level. If nothing is targeted, the manually set level is used."],
				order = 75,
				get = function() return settings["HitTarget"] end,
				set = DrD_Set("HitTarget"),
			},
			TargetLevel = {
				type = 'range',
				name = L["Target level"],
				desc = L["Target level compared to your level. Set as 3 for boss calculation."],
				min = -10,
				max = 10,
				step = 1,
				order = 80,
				get = function() return settings["TargetLevel"] end,
				set = DrD_Set("TargetLevel"),
			},
			TargetPlayer = {
				type = 'toggle',
				name = L["Target is a player"],
				desc = L["Manually set target level is calculated as a player."],
				order = 85,
				get = function() return settings["TargetPlayer"] end,
				set = DrD_Set("TargetPlayer"),
			},
			ManaConsumables = {
				type = 'toggle',
				name = L["Include mana consumables"],
				desc = L["Assumes use of maximum level mana consumables in regen calculations."],
				order = 90,
				get = function() return settings["ManaConsumables"] end,
				set = DrD_Set("ManaConsumables"),
			},			
			Custom = {
				type = 'toggle',
				name = L["Use custom stats"],
				desc = L["Manually set stats are used in calculations."],
				order = 95,
				get = function() return settings["Custom"] end,
				set = DrD_Set("Custom"),
			},
			CustomAdd = {
				type = 'toggle',
				name = L["Add custom stats"],
				desc = L["Manually set stats are added to your stats in calculations."],
				order = 100,
				get = function() return settings["CustomAdd"] end,
				set = DrD_Set("CustomAdd"),
			}, 				
			SpellPower = {
				type = 'range',
				name = L["Spell power"],
				desc = L["Input spell power to use in calculations."],
				min = -20000,
				max = 20000,
				step = 1,
				order = 105,
				get = function() return settings["SpellDamage"] end,
				set = DrD_Set("SpellDamage"),
			},					
			CritRating = {
				type = 'range',
				name = L["Critical strike rating"],
				desc = L["Input critical strike rating to use in calculations."],
				min = -2000,
				max = 2000,
				step = 1,
				order = 110,
				get = function() return settings["CritRating"] end,
				set = DrD_Set("CritRating"),					
			},
			HitRating = {
				type = 'range',
				name = L["Hit rating"],
				desc = L["Input hit rating to use in calculations."],
				min = -1000,
				max = 1000,
				step = 1,
				order = 115,
				get = function() return settings["HitRating"] end,
				set = DrD_Set("HitRating"),	
			},
			HasteRating = {
				type = 'range',
				name = L["Haste rating"],
				desc = L["Input haste rating to use in calculations."],
				min = -5000,
				max = 5000,
				step = 1,
				order = 120,
				get = function() return settings["HasteRating"] end,
				set = DrD_Set("HasteRating"),	
			},
			ManaPer5 = {
				type = 'range',
				name = L["Mana per 5"],
				min = -5000,
				max = 5000,
				step = 1,
				desc = L["Input MP5 to use in calculations."],
				order = 125,
				get = function() return settings["MP5"] end,
				set = DrD_Set("MP5"),	
			},
			Resilience = {
				type = 'range',
				name = L["Target resilience"],
				min = 0,
				max = 1500,
				step = 1,
				desc = L["Input your target's resilience."],
				order = 130,
				get = function() return settings["Resilience"] end,
				set = DrD_Set("Resilience"),	
			},
		},
	}
	if playerClass == "DEATHKNIGHT" then
		local types = table.ActionBar.args.DisplayType.validate
		types["DPM"] = nil
		types["MPS"] = nil
		types["ManaCost"] = nil
		types["TrueManaCost"] = nil
		table.ActionBar.args.CastsLeftDmg = nil
		table.Calculation.args.SpellPower = nil
		table.Calculation.args.ManaConsumables = nil
		table.Calculation.args.ManaPer5 = nil
	end	
	local calcTable = table.Calculation.args	
	if playerHealer then
		table.ActionBar.args.CastsLeft = {
			type = 'toggle',
			name = L["Healing spell casts"],
			desc = L["Actionbar displays the amount of healing spell casts left with current mana pool."],
			order = 105,
			get = function() return settings["CastsLeft"] end,
			set = function(v)
				settings.CastsLeft = v
				if v then
					if not self:IsBucketEventRegistered("UNIT_MANA") then
						self:RegisterBucketEvent("UNIT_MANA", 2)
					end
				else
					if self:IsBucketEventRegistered("UNIT_MANA") and not settings.CastsLeftDmg then
						self:UnregisterBucketEvent("UNIT_MANA")
					end
				end
				self:CancelSpellUpdate()
				self:UpdateAB()				
			end,
		}		
	end
end

local displayTypeTable = { ["Avg"] = 1, ["AvgTotal"] = 1, ["AvgHit"] = 2, ["AvgHitTotal"] = 2, ["MinHit"] = 3, ["MaxHit"] = 4, ["Max"] = 5, ["DPS"] = 6, ["DPSC"] = 6, ["DPSCD"] = 6, ["DPM"] = 2, ["MPS"] = 2, ["ManaCost"] = 2, ["TrueManaCost"] = 2, }

--Remove certain display types from death knights
if playerClass == "DEATHKNIGHT" then 
	displayTypeTable["DPM"] = nil
	displayTypeTable["MPS"] = nil
	displayTypeTable["ManCost"] = nil
	displayTypeTable["TrueManaCost"] = nil
end

function DrDamage:Caster_Data()
	if not playerHybrid then 
		self.ClassSpecials[GetSpellInfo(5019)] =  function()
			if not HasWandEquipped() then return "" end
			local speed, lowDmg, hiDmg = UnitRangedDamage("player")
			local avgDmg = (lowDmg + hiDmg) / 2
			local avgTotal = avgDmg * (1 + 0.005 * GetRangedCritChance())
			return math_floor(0.5 + select(displayTypeTable[settings.DisplayType] or 2, avgTotal, avgDmg, lowDmg + 0.5, hiDmg, hiDmg * 1.5, avgTotal / speed))
		end
	end
end

function DrDamage:Caster_OnEnable()
	if self.Caster_Options then
		self:Caster_Options()
		self.Caster_Options = nil
	end
	if self.Caster_Data then
		self:Caster_Data()
		self.Caster_Data = nil
	end
	if settings.ABText and (settings.CastsLeft or settings.CastsLeftDmg) then
		self:RegisterBucketEvent("UNIT_MANA", 2)
	end
	if not displayTypeTable[settings.DisplayType] then
		settings.DisplayType = "AvgTotal"
	end
	
	DrD_ClearTable = self.ClearTable
	DrD_BuffCalc = self.BuffCalc
	DrD_Round = self.Round
	DrD_MatchData = self.MatchData	
	spellInfo = self.spellInfo
	PlayerAura = self.PlayerAura
	TargetAura = self.TargetAura
	Consumables = self.Consumables
	Calculation = self.Calculation
	SetBonuses = self.SetBonuses
end

function DrDamage:Caster_OnProfileEnable()
	settings = self.db.profile
end

local oldValues = 0
function DrDamage:Caster_CheckBaseStats()
	local newValues = 0

	for i = 1, 7 do
		newValues = newValues + GetSpellBonusDamage(i)
		newValues = newValues + GetSpellCritChance(i)
	end

	newValues = newValues + GetSpellBonusHealing() + GetCombatRatingBonus(8) + GetCombatRatingBonus(20) + UnitStat("player",5)

	if newValues ~= oldValues then
		oldValues = newValues
		return true
	end

	return false	
end

local playerMana = UnitPower("player",0)
local trigger = 20 + UnitLevel("player") * 3
function DrDamage:UNIT_MANA( units )
	if units["player"] then
		if math_abs(UnitPower("player",0) - playerMana) >= trigger then
			playerMana = UnitPower("player", 0)
			if not DrDamage:IsEventScheduled("DrD_FullUpdate") then
				self:CancelSpellUpdate()
				self:UpdateAB()
			end
		end
	end
end

--Static tables
local schoolTable = { ["Holy"] = 2, ["Fire"] = 3, ["Nature"] = 4, ["Frost"] = 5, ["Shadow"] = 6, ["Arcane"] = 7 }
local debuffMods = { [GetSpellInfo(37577)] = true, [GetSpellInfo(43951)] = true } --Debilitating Strike, Bonegrinder, TODO: What else?
local potion_sickness = GetSpellInfo(53787)

--Values you can modify with the modify table (4th arg in DrDamage:CasterCalc)
local modifyTable = { "spellDmg", "dmgM", "critPerc", "hitPerc", "manaRegen", "finalMod", "manaMod", "castTime" }

--Temporary tables
local calculation = {}
local ActiveAuras = {}
local BuffTalentRanks = {}
local CalculationResults = {}

function DrDamage:CasterCalc( name, rank, tooltip, modify, debug )
	if not spellInfo or not name then return end
	if not rank then
		_, rank = GetSpellInfo(name)
	elseif tonumber(rank) and GetSpellInfo(name) then
		rank = string_gsub(select(2,GetSpellInfo(name)),"%d+", rank)
	end

	--Base spell information
	local spell, baseSpell, spellName, spellTalents
	
	if spellInfo[name]["Secondary"] and (settings.SwapCalc and (not tooltip or not IsShiftKeyDown()) or not settings.SwapCalc and tooltip and IsShiftKeyDown()) then
		spell = spellInfo[name]["Secondary"][(rank and tonumber(string_match(rank,"%d+"))) or 1]
		baseSpell = spellInfo[name]["Secondary"][0]
		spellName = spellInfo[name]["Secondary"]["Name"]
		spellTalents = spellInfo[name]["Secondary"]["Talents"]
	else
		baseSpell = spellInfo[name][0]
		if type(baseSpell) == "function" then
			baseSpell, spell, spellName = baseSpell()
			spellTalents = spellInfo[spellName]["Talents"]
		else
			spell = spellInfo[name][(rank and tonumber(string_match(rank,"%d+"))) or 1]
			spellName = spellInfo[name]["Name"]
			spellTalents = spellInfo[name]["Talents"]
		end
	end
	
	if not spell then return end
	
	local healingSpell = DrD_MatchData(baseSpell.School, "Healing")					--Healing spell (boolean)
	
	calculation.name = name										--Ability name, localized
	calculation.spellName = spellName								--Ability name, enUS
	calculation.tooltipName = nil
	calculation.healingSpell = healingSpell								--Healing spell, boolean
	calculation.minDam = spell[1]									--Spell initial base min damage
	calculation.maxDam = spell[2]									--Spell initial base max damage
	calculation.castTime = spell.castTime or baseSpell.castTime or 1.5				--Cast time
	calculation.eDuration = spell.eDuration or baseSpell.eDuration or 1				--Effect duration
	calculation.cooldown = baseSpell.Cooldown or 0							--Spell's cooldown
	calculation.instant = nil
	calculation.sHits = spell.sHits or baseSpell.sHits						--Amount of hits of spell
	calculation.sTicks = spell.sTicks or baseSpell.sTicks						--Time between ticks of the spell
	calculation.canCrit = baseSpell.canCrit								--Crits true/false
	calculation.hybridCanCrit = false
	calculation.hybridDotDmg = spell.hybridDotDmg							--DoT portion of hybrid spells
	calculation.extraDotDmg = spell.extraDotDmg
	calculation.extraChance = 1
	calculation.extraChanceCrit = nil
	calculation.extraTicks = nil
	calculation.extraCrit = nil
	calculation.extraCanCrit = nil
	calculation.extraName = nil
	calculation.extra = 0
	calculation.bDmgM = 1										--Base multiplier
	calculation.dmgM = 1										--Final multiplier
	calculation.dmgM_custom = nil
	calculation.dmgM_dot = 1									--Final multiplier to DoT portion
	calculation.dmgM_dd = 1										--Final coefficient multiplier for hybrid spells
	calculation.dmgM_dd_Add = 0									--Final coefficient additive modifier for hybrid spells
	calculation.dmgM_dot_Add = 0									--Talents: DoT Damage multiplier additive	
	calculation.dmgM_Add = 0									--Talents: Damage multiplier additive
	calculation.dmgM_Add_Spell = 0									--Talents: Spell specific additive multiplier
	calculation.finalMod = 0 									--Modifier to final damage +/-
	calculation.finalMod_M = 1									--Modifier to calculated total damage
	--calculation.finalMod_fM = 0									--Modifier to final damage with dmgM coefficient
	--calculation.finalMod_sM = 0									--Modifier to final damage with spellDmgM coefficient
	--calculation.finalMod_dot = 0									--Modifier to final dot damage +/-
	calculation.manaCost = select(4,GetSpellInfo(name,rank)) or 0
	calculation.powerType = select(6,GetSpellInfo(name,rank))
	calculation.manaRegen = select(2, GetManaRegen("player"))					--Mana regen while casting
	calculation.manaMod = 0
	calculation.chainFactor = baseSpell.chainFactor							--Chain effect spells
	calculation.chainTargets = 3
	calculation.freeCrit = 0
	calculation.leechBonus = baseSpell.Leech or 0
	calculation.haste = GetCombatRating(20)
	calculation.spellAPBonus = baseSpell.SpellAPBonus or 0
	calculation.AP = baseSpell.SpellAPBonus and self:GetAP() or 0
		
	if type(baseSpell.School) == "table" then
		calculation.school = baseSpell.School[1]
		calculation.group = baseSpell.School[2]
		calculation.subType = baseSpell.School[3]
	else
		calculation.school = baseSpell.School
		calculation.group = nil
		calculation.subType = nil
	end

	--CORE: Spell damage and coefficients
	calculation.spellDmg = healingSpell and GetSpellBonusHealing() or GetSpellBonusDamage(schoolTable[calculation.school] or 1)
	calculation.spellDmg_dd = 0
	calculation.spellDmg_dot = 0
	calculation.spellDmgM = (healingSpell and 1.88 or 1) * (baseSpell.bonusFactor or baseSpell.eDot and (calculation.eDuration / 15) or (calculation.castTime / 3.5))
	calculation.spellDmgM_Add = 0
	calculation.spellDmgM_dot = (baseSpell.dotFactor or calculation.eDuration / 15)
	
	--CORE: Factor for downranking, factor for spells under level 20
	calculation.penaltyMod = spell.spellLevel and (spell.spellLevel < 20) and not baseSpell.NoLowLevelPenalty and (1 - ((20 - spell.spellLevel) * 0.0375)) or 1
	
	--[[
	if spell.spellLevel and not baseSpell.NoDownRank then
		--local oldmod = math_min(1, (spell.spellLevel + 11 + (spell.Downrank or baseSpell.Downrank or 0)) / UnitLevel("player")) or 1		
		local mod = math_min(1,math_max(0,1 - (UnitLevel("player") - spell.spellLevel - 11) * 0.05))
		calculation.penaltyMod = calculation.penaltyMod * mod
		--if not baseSpell.sHits and not baseSpell.eDot and not baseSpell.NoDownRank then
		--		calculation.dmgM = calculation.dmgM * math_min(1,1 - (UnitLevel("player") - spell.spellLevel - 5) * 0.0125)
		--end
	end
	--]]	
	
	--CORE: Calculate crit/hit
	calculation.critM = baseSpell.MeleeCrit and 1 or 0.5
	calculation.critPerc = baseSpell.MeleeCrit and GetCritChance() or GetSpellCritChance(schoolTable[calculation.school] or 1)
	calculation.hitPerc = baseSpell.MeleeHit and DrDamage:GetMeleeHit(nil,true) or DrDamage:GetSpellHit()
	
	--CORE: Calculate modified cast time
	local ct = select(7, GetSpellInfo(name,rank))
	if not ct or ct == 0 then
		calculation.castTime = 0.00015 * (select(7,GetSpellInfo(18960)))
		if baseSpell.Channeled then
			calculation.castTime = (spell.castTime or baseSpell.castTime)/(1.5/calculation.castTime)
		else
			calculation.instant = true
		end
	else
		calculation.castTime = ct/1000
	end
	
	--CORE: Manual variables from profile:
	if settings.Custom then
		local rhaste = 1.5/(0.00015*(select(7,GetSpellInfo(18960))))
		if settings.CustomAdd then
			calculation.spellDmg = math_max(0, calculation.spellDmg + settings.SpellDamage)
			calculation.manaRegen = math_max(0, calculation.manaRegen + settings.MP5/5)
			calculation.castTime = (calculation.castTime * rhaste)/math_max(1,(rhaste + 0.01 * self:GetRating("SpellHaste", settings.HasteRating, true)))
			calculation.haste = calculation.haste + settings.HasteRating
			calculation.critPerc = calculation.critPerc + self:GetRating("Crit", settings.CritRating, true)
			calculation.hitPerc = calculation.hitPerc + self:GetRating((baseSpell.MeleeHit and "MeleeHit" or "Hit"), settings.HitRating, true)
		else
			calculation.spellDmg = math_max(0, settings.SpellDamage)
			calculation.manaRegen = math_max(0, settings.MP5/5)
			calculation.castTime = (calculation.castTime * rhaste)/math_max(1,(rhaste - GetCombatRatingBonus(20)/100 + 0.01 * self:GetRating("SpellHaste", settings.HasteRating, true)))
			calculation.haste = settings.HasteRating
			calculation.critPerc = calculation.critPerc - GetCombatRatingBonus((baseSpell.MeleeCrit and 9 or 11)) + self:GetRating("Crit", settings.CritRating, true)		
			calculation.hitPerc = calculation.hitPerc - GetCombatRatingBonus((baseSpell.MeleeHit and 6 or 8)) + self:GetRating((baseSpell.MeleeHit and "MeleeHit" or "Hit"), settings.HitRating, true)			
		end
	end
	
	--CORE: Add mana potions if not potion sickness
	if settings.ManaConsumables and not UnitDebuff("player", potion_sickness) then
		calculation.manaMod = calculation.manaMod + 4300
	end
	
	--CORE: Cap instants to 1.5s GCD before talent calculation
	if calculation.instant and calculation.castTime > 1.5 then
		calculation.castTime = 1.5
	end
	
	--CORE: Random item bonuses to base values not supplied by API
	if self.RelicSlot and self.RelicSlot[spellName] then
		local data = self.RelicSlot[spellName]
		local count = #data
		if count then
			for i = 1, count - 1, 2 do
				if data[i] and data[i+1] then
					if IsEquippedItem(data[i]) or debug then
						local modType = data["ModType"..((i+1)/2)]

						if not modType then
							calculation.spellDmg = calculation.spellDmg + data[i+1]
						elseif calculation[modType] then
							calculation[modType] = calculation[modType] + data[i+1]
						elseif modType == "Base" then
							calculation.minDam = calculation.minDam + data[i+1]
							calculation.maxDam = calculation.maxDam + data[i+1]
						end
					end
				end
			end
		end
	end	
	
	--CORE: Base value increase after levelups:
	if baseSpell.BaseIncrease then
		local playerLevel = UnitLevel("player")
	
		if playerLevel > spell.spellLevel then
			if playerLevel >= 80 then
				calculation.minDam = calculation.minDam + spell[3]
				calculation.maxDam = calculation.maxDam + spell[4]			
			else
				local diff = 80 - spell.spellLevel
				
				calculation.minDam = calculation.minDam + (playerLevel - spell.spellLevel) * (spell[3] / diff)
				calculation.maxDam = calculation.maxDam + (playerLevel - spell.spellLevel) * (spell[4] / diff)
			end
		end
		
		calculation.minDam = math_floor(calculation.minDam)
		calculation.maxDam = math_ceil(calculation.maxDam)
	end	
	
	--CORE: Apply talents
	for i=1,#spellTalents do
		local talentValue = spellTalents[i]
		local modType = spellTalents[("ModType" .. i)]
		local mod = spellTalents["Mod"..i]

		if calculation[modType] then
			if spellTalents[("Multiply" .. i)] then
				calculation[modType] = calculation[modType] * (1 + talentValue)
			else
				calculation[modType] = calculation[modType] + talentValue
			end			
		elseif self.Calculation[modType] then
			self.Calculation[modType](calculation, talentValue)
		else
			BuffTalentRanks[modType] = talentValue
		end
		if mod then
			local _, _, _, _, cur = GetTalentInfo(mod[2],mod[3])
			if mod[6] - cur ~= 0 then
				calculation[mod[4]] = mod[5](calculation[mod[4]], cur, mod[6])
			end
		end		
	end	
	
	--Process modify table, nothing needs this atm
	--[[
	if self.CasterGlobalModify then
		local modify = self.CasterGlobalModify
		for _, v in ipairs( modifyTable ) do
			local value, set = 0
			if modify[calculation.school] and modify[calculation.school][v] then
				value = modify[calculation.school][v]
				set = modify[calculation.school].Set
			end
			if modify[calculation.group] and modify[calculation.group][v] then
				value = modify[calculation.school].Set and modify[calculation.group][v] or (value + modify[calculation.group][v])
				set = set or modify[calculation.school].Set
			end
			if modify[spellName] and modify[spellName][v] then
				value = modify[spellName].Set and modify[spellName][v] or (value + modify[spellName][v])
				set = set or modify[spellName].Set			
			end
			if value then
				calculation[v] = math_max(0, set and value or (calculation[v] + value))
			end
		end
	end
	if modify and type(modify) == "table" then
		for _, v in ipairs( modifyTable ) do
			if modify[v] then
				if modify.Set then
					calculation[v] = math_max(0, modify[v])
				else
					calculation[v] = math_max(0, calculation[v] + modify[v])
				end
			end
		end
	end
	--]]
	
	--CORE: Buffs/Debuffs
	if not baseSpell.NoAura then
		local mod = not healingSpell and (select(7, UnitDamage("player")) or 1) * (self.casterMod or 1)
		if mod and mod > 0 then calculation.dmgM = calculation.dmgM * mod end
		if next(settings["PlayerAura"]) or debug then
			for buffName in pairs(debug and PlayerAura or settings["PlayerAura"]) do
				local aura = PlayerAura[buffName]
				if aura and not aura.Update and not aura.Melee then
					DrD_BuffCalc( aura, calculation, ActiveAuras, BuffTalentRanks, baseSpell, buffName, nil, nil, nil, aura.Ranks )
				end
			end
		end
		if next(settings["TargetAura"]) or debug then
			for buffName in pairs(debug and TargetAura or settings["TargetAura"]) do
				local aura = TargetAura[buffName]
				if aura and not aura.Update and not aura.Melee then
					DrD_BuffCalc( aura, calculation, ActiveAuras, BuffTalentRanks, baseSpell, buffName, nil, nil, nil, aura.Ranks )
				end
			end
		end	
		--BUFF/DEBUFF -- DAMAGE/HEALING -- PLAYER
		for index=1,40 do
			local buffName, rank, texture, apps = UnitBuff("player",index)
			if buffName then
				local aura = PlayerAura[buffName]
				if aura and not settings["PlayerAura"][buffName] and not aura.Update and not aura.Melee then
					DrD_BuffCalc( aura, calculation, ActiveAuras, BuffTalentRanks, baseSpell, buffName, index, apps, texture, rank )
				end
			else break end		
		end
		--DEBUFF -- DAMAGE/HEALING -- PLAYER
		for index=1,40 do
			local buffName, rank, texture, apps = UnitDebuff("player",index)
			if buffName then
				local aura = PlayerAura[buffName]
				if aura and not settings["PlayerAura"][buffName] and not aura.Update and not aura.Melee then
					DrD_BuffCalc( aura, calculation, ActiveAuras, BuffTalentRanks, baseSpell, buffName, index, apps, texture, rank )
				end
				if not healingSpell and debuffMods[buffName] then
					GT:SetUnitDebuff("player", index)
					local _, _, amount = GT:Find("(%d+)%%")
					if amount then calculation.dmgM = calculation.dmgM / (1-tonumber(amount)/100) end
				end				
			else break end			
		end	
		--DEBUFF -- DAMAGE/HEALING -- TARGET
		local debuffTarget = (not healingSpell or UnitIsFriend("player", "target")) and "target" or UnitIsFriend("player", "targettarget") and "targettarget"
		if debuffTarget and not baseSpell.NoDebuffs then
			for index=1,40 do 
				local buffName, rank, texture, apps = UnitDebuff(debuffTarget,index)
				if buffName then
					local aura = TargetAura[buffName]
					if aura and not settings["TargetAura"][buffName] and not aura.Update and not aura.Melee then
						DrD_BuffCalc( aura, calculation, ActiveAuras, BuffTalentRanks, baseSpell, buffName, index, apps, texture, rank )
					end
				else break end
			end
		end
		--BUFF - HEALING -- TARGET
		if healingSpell and playerHealer then
			local healingTarget = not UnitExists("target") and "player" or UnitIsFriend("target","player") and "target" or UnitIsFriend("targettarget","player") and "targettarget"
			if healingTarget then
				for index=1,40 do 
					local buffName, rank, texture, apps = UnitBuff(healingTarget,index)
					if buffName then
						local aura = TargetAura[buffName]
						if aura and not settings["TargetAura"][buffName] and not aura.Update and not aura.Melee then
							DrD_BuffCalc( aura, calculation, ActiveAuras, BuffTalentRanks, baseSpell, buffName, index, apps, texture, rank, healingTarget )
						end
					else break end
				end
			end
		end		
	end
	--CORE: Add manually selected consumables if not active
	if next(settings["Consumables"]) then
		for buffName in pairs(settings["Consumables"]) do
			if Consumables[buffName] then
				DrD_BuffCalc( Consumables[buffName], calculation, ActiveAuras, BuffTalentRanks, baseSpell, buffName )
			end
		end
	end	

	if SetBonuses[playerClass] then
		SetBonuses[playerClass]( calculation, ActiveAuras, BuffTalentRanks, spell, baseSpell )
	end
	if Calculation[playerClass] then
		Calculation[playerClass]( calculation, ActiveAuras, BuffTalentRanks, spell, baseSpell )
	end	
	if SetBonuses[spellName] then
		SetBonuses[spellName]( calculation, ActiveAuras, BuffTalentRanks, spell, baseSpell )
	end
	if Calculation[spellName] then
		Calculation[spellName]( calculation, ActiveAuras, BuffTalentRanks, spell, baseSpell )
	end
	
	--CORE: Crit modifier
	if baseSpell.MeleeCrit then
		calculation.critM = calculation.critM + 2 * self.Damage_critMBonus * calculation.critM
	else
		calculation.critM = calculation.critM + 3 * (healingSpell and self.Healing_critMBonus or self.Damage_critMBonus) * calculation.critM
	end	
	
	--CORE: Damage modifier
	if not calculation.dmgM_custom then
		calculation.dmgM_dot = calculation.dmgM_dot * calculation.dmgM * (1 + calculation.dmgM_Add + calculation.dmgM_dot_Add)
		calculation.dmgM = baseSpell.eDot and calculation.dmgM_dot or calculation.dmgM * (1 + calculation.dmgM_Add) * (1 + calculation.dmgM_Add_Spell)
	end
	
	--CORE: Sum spell power coefficient
	calculation.spellDmgM = (calculation.spellDmgM + calculation.spellDmgM_Add) * (baseSpell.sFactor or 1) * calculation.penaltyMod
	
	--CORE: Reset cooldown if <= 0
	if calculation.cooldown <= 0 then
		calculation.cooldown = nil
	end
	
	--CORE: Resilience
	if settings.Resilience > 0 and not calculation.healingSpell then
		calculation.critPerc = math_max(0, calculation.critPerc - settings.Resilience / 82)
		calculation.critM = calculation.critM * math_max(2/3 - 1/(3 * calculation.critM), 1 - 0.044 * (settings.Resilience / 82))
		if calculation.hybridDotDmg then
			calculation.dmgM_dot = calculation.dmgM_dot * (1 - math_min(1, 0.01 * settings.Resilience / 82))
		elseif baseSpell.eDot then
			calculation.dmgM = calculation.dmgM * (1 - math_min(1, 0.01 * settings.Resilience / 82))
		end
	end
	
	local returnAvgTotal = DrD_DmgCalc( baseSpell, spell, false, false, tooltip )
	
	if tooltip then
		if settings.Next or settings.CompareDmg or settings.CompareCrit or settings.CompareHit or settings.CompareHaste then
			if calculation.canCrit then
				calculation.critPerc = calculation.critPerc + 1
				CalculationResults.NextCrit = DrD_Round( DrD_DmgCalc( baseSpell, spell, true ) - returnAvgTotal, 1 )
				calculation.critPerc = calculation.critPerc - 1	
			end
			if (calculation.spellDmgM > 0) or (calculation.spellDmgM_dot > 0) then
				calculation.spellDmg = calculation.spellDmg + 10
				CalculationResults.NextTenDmg = DrD_Round( DrD_DmgCalc( baseSpell, spell, true ) - returnAvgTotal, 1 )
				calculation.spellDmg = calculation.spellDmg - 10
			end
			if not healingSpell and not baseSpell.Unresistable then
				local temp = settings.HitCalc and returnAvgTotal or DrD_DmgCalc( baseSpell, spell, true, true )
				calculation.hitPerc = calculation.hitPerc + 1
				CalculationResults.NextHit = DrD_Round( DrD_DmgCalc( baseSpell, spell, true, true ) - temp, 1 )
			end	
		end		
	end
	
	DrD_ClearTable( BuffTalentRanks )
	DrD_ClearTable( ActiveAuras )
	
	local text
	
	if baseSpell.NoDPS and (settings.DisplayType == "DPS" or settings.DisplayType == "DPSC") then
		text = CalculationResults["AvgTotal"]
	else
		text = CalculationResults[settings.DisplayType]
	end

	return text, CalculationResults, debug and calculation
end

local function DrD_FreeCrits( casts, calculation )
	local total = casts
	local mod = calculation.freeCrit * calculation.critPerc / 100
	for i = 1, 5 do
		casts = math_floor(mod * casts)
		if casts == 0 then break end									
		total = total + casts
	end
	return total
end

local ManaCalc = { ["DPM"] = true, ["ManaCost"] = true, ["TrueManaCost"] = true, ["MPS"] = true, }

DrD_DmgCalc = function( baseSpell, spell, nextCalc, hitCalc, tooltip )
	--CORE: Initialize variables		
	local baseDuration = spell.eDuration or baseSpell.eDuration
	local baseHits = spell.sHits or baseSpell.sHits
	local modDuration = baseDuration and (calculation.eDuration > baseDuration) and (1 + (calculation.eDuration - baseDuration) / baseDuration) or 1
	local modHits = baseHits and (calculation.sHits > baseHits) and (1 + (calculation.sHits - baseHits) / baseHits) or 1
	local dispSpellDmgM = calculation.spellDmgM * modHits
	
	--CORE: Basic min/max calculation
	local calcMinDmg = calculation.dmgM_dd * (calculation.dmgM + calculation.dmgM_dd_Add) * (calculation.bDmgM * calculation.minDam * (calculation.sHits or 1) + (modHits * ((calculation.spellDmg + calculation.spellDmg_dd) * calculation.spellDmgM + calculation.AP * calculation.spellAPBonus))) + calculation.finalMod --+ calculation.finalMod_fM * calculation.dmgM + calculation.finalMod_sM * calculation.spellDmgM 
	local calcMaxDmg = calculation.dmgM_dd * (calculation.dmgM + calculation.dmgM_dd_Add) * (calculation.bDmgM * calculation.maxDam * (calculation.sHits or 1) + (modHits * ((calculation.spellDmg + calculation.spellDmg_dd) * calculation.spellDmgM + calculation.AP * calculation.spellAPBonus))) + calculation.finalMod --+ calculation.finalMod_fM * calculation.dmgM + calculation.finalMod_sM * calculation.spellDmgM 
		
	--CORE: Effects extended by talents. (Imp. SW:P etc.)
	if baseSpell.eDot and (modDuration > 1) then
		calcMinDmg = calcMinDmg * modDuration
		calcMaxDmg = calcMaxDmg * modDuration
		dispSpellDmgM = dispSpellDmgM * modDuration
	end		

	--CORE: Calculate average
	local calcAvgDmg = (calcMinDmg + calcMaxDmg) / 2

	--CORE: Critical hits
	local calcAvgCrit
	local calcAvgDmgCrit
	local calcMinCrit
	local calcMaxCrit
	local critBonus = 0
	if calculation.canCrit then
		calculation.critPerc = math_max(0,math_min(100, calculation.critPerc))
		calcMinCrit = calcMinDmg + calcMinDmg * calculation.critM
		calcMaxCrit = calcMaxDmg + calcMaxDmg * calculation.critM
		critBonus = (calculation.critPerc / 100) * calcAvgDmg * calculation.critM
		calcAvgCrit = (calcMinCrit + calcMaxCrit) / 2
		calcAvgDmgCrit = calcAvgDmg + critBonus
	else
		calcAvgCrit = calcAvgDmg
		calcAvgDmgCrit = calcAvgDmg
		calculation.critPerc = 0
	end

	--DOT: Hybrid
	local calcDotDmg = 0
	local critBonus_dot = 0
	local hybridDmgM
	local perHit, ticks	
	if calculation.hybridDotDmg then
		hybridDmgM = calculation.spellDmgM_dot * (baseSpell.sFactor or 1) * calculation.penaltyMod
		calcDotDmg = calculation.dmgM_dot * ( hybridDmgM * (calculation.spellDmg + calculation.spellDmg_dot) + calculation.hybridDotDmg ) * modDuration
		dispSpellDmgM = dispSpellDmgM + hybridDmgM * modDuration
		if calculation.hybridCanCrit then
			critBonus_dot = calcDotDmg * (calculation.critPerc / 100) * calculation.critM
			calcDotDmg = calcDotDmg + critBonus_dot
		end
	end
	
	--DOT: Extra DoT, eg. fireball. Uncomment extraDotF if functionality ever needed.
	if calculation.extraDotDmg then
		calcDotDmg = calcDotDmg + calculation.dmgM_dot * calculation.extraDotDmg * modDuration --* baseSpell.extraDotF * calculation.penaltyMod * calculation.spellDmg 
		--dispSpellDmgM = dispSpellDmgM + baseSpell.extraDotF * calculation.penaltyMod * modDuration	
	end
	
	--DOT: Final modifier
	--calcDotDmg = calcDotDmg + calculation.finalMod_dot
	
	--DOT: Resilience calculation
	if settings.Resilience > 0 and not calculation.healingSpell and (calcDotDmg > 0) then
		calcDotDmg = calcDotDmg * (1 - math_min(1,0.01 * settings.Resilience / 82))
	end	

	--SPECIAL: Chain effects (chain lightning, chain heal)
	local chainEffect
	if calculation.chainFactor then
		chainEffect = calcAvgDmgCrit * (calculation.chainFactor + calculation.chainFactor^2 + ((calculation.chainTargets > 3) and calculation.chainFactor^3 or 0))
		calcAvgDmgCrit = calcAvgDmgCrit + chainEffect
	end
	
	--SPECIAL: Stacking spells, not used by anything at the moment
	--[[if baseSpell.Stacks then
		if calculation.extraDotDmg then
			perHit = calcAvgDmgCrit
			ticks = "~" .. baseSpell.Stacks
			calcAvgDmgCrit = calcAvgDmgCrit + calculation.extraDotDmg * (baseSpell.Stacks - 1) * calculation.dmgM_dot
		end
	end
	--]]
	
	--SPECIAL: Final average modifiers (lightning overload) and effects from modules
	if calculation.extraCrit then calculation.extra = calcAvgCrit *  calculation.extraCrit end
	if calculation.extraChanceCrit then calculation.extraChance = calculation.critPerc / 100 end
	local extraAvg = calculation.extra * (calculation.extraCanCrit and (1 + calculation.critM * calculation.critPerc / 100) or 1) * calculation.extraChance
	calcAvgDmgCrit = calcAvgDmgCrit * calculation.finalMod_M + extraAvg
	
	--CORE: Hit calculation:
	local hitPenalty = 0
	local hitPenaltyAvg = 0
	calculation.hitPerc = math_max(0,math_min(100, calculation.hitPerc))
	if not calculation.healingSpell and not baseSpell.Unresistable then
		if settings.HitCalc or hitCalc then
			if settings.TwoRoll then
				hitPenalty = calcAvgDmgCrit * ((calculation.hitPerc / 100) - 1)
				hitPenaltyAvg = (calcAvgDmg + critBonus) * ((calculation.hitPerc / 100) - 1)
			else
				hitPenalty = (calcAvgDmgCrit - critBonus) * ((calculation.hitPerc / 100) - 1)
				hitPenaltyAvg = calcAvgDmg * ((calculation.hitPerc / 100) - 1)
			end
			calcAvgDmgCrit = calcAvgDmgCrit + hitPenalty - calcDotDmg * (1 - ( calculation.hitPerc / 100))
		end
	end
	
	local calcDPS, spamDmg, spamDPS
	if not nextCalc then
		--CORE: Minimum GCD 1s
		calculation.castTime = math_max(1,DrD_Round(calculation.castTime,3))
		--CORE: Per hit calculation
		if calculation.sHits then
			ticks = calculation.sHits
			perHit = calcAvgDmg / ticks
		elseif calculation.sTicks and (not spell.extraDotDmg or spell.extraDotDmg and not calculation.extraTicks) then
			ticks = ((calculation.castTime > calculation.eDuration) and (spell.castTime or baseSpell.castTime or 1.5) or calculation.eDuration) / calculation.sTicks
			perHit = ((calculation.hybridDotDmg or calculation.extraDotDmg) and (calcDotDmg - critBonus_dot) or calcAvgDmg) / ticks
			if baseSpell.DotCap then
				ticks = math_ceil(calculation.hybridDotDmg / perHit)
				calcDotDmg = ticks * perHit
				dispSpellDmgM =  dispSpellDmgM - hybridDmgM + (hybridDmgM / 6) * ticks
				calculation.eDuration = ticks * calculation.sTicks
			end
		elseif calculation.extraTicks then
			ticks = calculation.extraTicks
			perHit = calculation.extra / ticks
		end			
		--CORE: DPS calculation
		if baseSpell.eDot or calculation.hybridDotDmg then
			calcDPS = ( calcAvgDmgCrit + calcDotDmg ) / calculation.eDuration
			if baseSpell.DotStacks then
				spamDmg = (calcDotDmg - perHit) * (baseSpell.DotStacks)
				spamDPS = (calcDotDmg / calculation.eDuration) * (baseSpell.DotStacks)
			elseif calculation.hybridDotDmg then
				spamDmg = calcAvgDmgCrit + (calculation.sTicks and (math_floor((calculation.cooldown or calculation.castTime)/calculation.sTicks) * perHit) or 0)
				spamDPS = spamDmg / (calculation.cooldown or calculation.castTime)
			end
		elseif calculation.extraDotDmg then
			calcDPS = calcAvgDmgCrit / calculation.castTime + calcDotDmg / calculation.eDuration
		else
			calcDPS = calcAvgDmgCrit / calculation.castTime
		end
	end
	
	--CORE: Add dot portion to total average value
	if not baseSpell.NoDotAverage then
		calcAvgDmgCrit = calcAvgDmgCrit + calcDotDmg
	end
	
	if nextCalc then
		return calcAvgDmgCrit
	else
		DrD_ClearTable( CalculationResults )

		CalculationResults.Healing = 		calculation.healingSpell
		CalculationResults.Avg =		math_floor(calcAvgDmg + hitPenaltyAvg + critBonus + 0.5)
		CalculationResults.AvgTotal =  		math_floor(calcAvgDmgCrit + 0.5)
		CalculationResults.DPS = 		DrD_Round(calcDPS, 1)
		CalculationResults.DPSC = 		DrD_Round(calcAvgDmgCrit / calculation.castTime, 1)
		CalculationResults.DPSCD = 		calculation.cooldown and DrD_Round(calcAvgDmgCrit / calculation.cooldown, 1) or CalculationResults.DPS
		CalculationResults.AvgHit = 		math_floor((calculation.sHits and perHit or calcAvgDmg) + 0.5)
		CalculationResults.MinHit = 		math_floor(calcMinDmg)
		CalculationResults.MaxHit = 		math_ceil(calcMaxDmg)
		CalculationResults.AvgHitTotal = 	math_floor(calculation.sHits and ticks and (calcAvgDmgCrit/ticks) or (calcAvgDmg + calcDotDmg) + 0.5)
		CalculationResults.Max = 		math_ceil(calcDotDmg + (calcMaxCrit or calcMaxDmg) + (chainEffect or 0))
			
		--CORE: Write tooltip data
		if tooltip then
			if calculation.canCrit then
				CalculationResults.MinCrit =	math_floor( calcMinCrit )
				CalculationResults.MaxCrit = 	math_ceil( calcMaxCrit )
				CalculationResults.AvgCrit = 	math_floor( calcAvgCrit + 0.5 )
				CalculationResults.CritRate = 	DrD_Round( calculation.critPerc, 2 )
				CalculationResults.CritM =	DrD_Round( 100 + calculation.critM * 100, 2)
			end
			if calculation.spellAPBonus > 0 then
				CalculationResults.AP = 		math_floor( calculation.AP + 0.5 )
				CalculationResults.SpellAPBonus = 	DrD_Round( calculation.spellAPBonus, 3 )
			end
			if dispSpellDmgM > 0 then
				CalculationResults.SpellDmg = 	math_floor( calculation.spellDmg + 0.5 )
				CalculationResults.SpellDmgM = 	DrD_Round( dispSpellDmgM, 3 )			
			end
			
			CalculationResults.HitRate = 	DrD_Round( calculation.hitPerc, 2 )
			CalculationResults.DotDmg = 	math_floor( calcDotDmg + 0.5 )
			CalculationResults.DmgM = 	DrD_Round( calculation.dmgM, 3 )
			CalculationResults.Haste = 	math_max(0,calculation.haste)
			CalculationResults.Cooldown = 	calculation.cooldown			
			CalculationResults.CastTime = 	calculation.castTime
			
			
			if calculation.tooltipName then
				CalculationResults.Name = calculation.tooltipName
			end
			if spamDPS then
				CalculationResults.SpamDPS =	DrD_Round( spamDPS, 1 )
			end
			if chainEffect then 
				CalculationResults.ChainDmg = 	math_floor( chainEffect + 0.5 ) 
			end
			if calculation.extra > 0 then
				CalculationResults.Extra =	math_floor( calculation.extra + 0.5 )
				CalculationResults.ExtraName =	calculation.extraName
				if calculation.extraChance < 1 or calculation.extraCanCrit then
					CalculationResults.ExtraAvg =	math_floor( extraAvg + 0.5 )
					if calculation.extraCrit and calcMaxCrit then
						CalculationResults.ExtraMax = math_floor( calculation.extraCrit * calcMaxCrit * (calculation.extraCanCrit and (1 + calculation.critM) or 1) + 0.5 )
					end
				end
				--CalculationResults.ExtraDPS = 	DrD_Round( extraDPS, 1 )
			end
			if perHit and ticks then
				if calculation.canCrit and not calculation.hybridDotDmg and not calculation.extraDotDmg and not calculation.extraTicks or calculation.hybridCanCrit and calculation.hybridDotDmg then
					CalculationResults.PerCrit = DrD_Round( perHit + calculation.critM * perHit, 1 )
					CalculationResults.Crits = math_floor( ticks * (calculation.critPerc / 100) + 0.5 )
					ticks = ticks - CalculationResults.Crits
				end
				CalculationResults.PerHit = DrD_Round( perHit, 1 )
				CalculationResults.Hits = ticks
			end				
			if baseSpell.Leech and calculation.leechBonus > 1 or not baseSpell.Leech and calculation.leechBonus > 0 then
				CalculationResults.AvgLeech = DrD_Round( calcAvgDmg * calculation.leechBonus, 1 )
				if perHit then
					CalculationResults.PerHitHeal =  DrD_Round( perHit * calculation.leechBonus, 1 )
				end
			end
		end
		if (tooltip or ManaCalc[settings.DisplayType]) and calculation.powerType == 0 then
			local manaCost = calculation.manaCost
			
			if manaCost == 0 then
				CalculationResults.castsBase = "\226\136\158"
				CalculationResults.castsRegen = 0
				CalculationResults.ManaCost = 0
				CalculationResults.TrueManaCost = 0
				CalculationResults.DPM = "\226\136\158"
				CalculationResults.MPS = 0
			else
				CalculationResults.ManaCost = manaCost
				CalculationResults.TrueManaCost = DrD_Round(manaCost * (1 - ((calculation.canCrit and calculation.critPerc or 0)/100) * calculation.freeCrit), 1)
				CalculationResults.DPM = DrD_Round(calcAvgDmgCrit / CalculationResults.TrueManaCost, 1)
				CalculationResults.MPS = DrD_Round(CalculationResults.TrueManaCost / calculation.castTime, 1)
				
				if tooltip then
					local castTime = math_max((calculation.cooldown or 0), calculation.castTime)
					local PlayerMana = UnitPower("player",0) 
					local base_casts = (calculation.freeCrit > 0) and calculation.canCrit and DrD_FreeCrits(math_floor(PlayerMana / manaCost), calculation) or math_floor(PlayerMana / manaCost)
					local casts = base_casts
					local regen_speed = calculation.manaRegen
					local regen_casts = math_floor(calculation.manaMod / manaCost)
					local regen_total = regen_casts * castTime * regen_speed					

					if castTime <= 10 then
						for i = 1, 5 do
							local regen_new = casts * castTime * regen_speed
							casts = (calculation.freeCrit > 0) and calculation.canCrit and DrD_FreeCrits(math_floor(regen_new / manaCost), calculation) or math_floor(regen_new / manaCost)
							regen_total = regen_total + regen_new
							regen_casts = regen_casts + casts
							if casts == 0 then break end
						end

						CalculationResults.SOOM = DrD_Round((base_casts + regen_casts) * castTime, 1)
					end
					
					if spamDmg then
						CalculationResults.SpamDPM = DrD_Round(spamDmg / CalculationResults.TrueManaCost, 1)
					end
					if (base_casts + regen_casts) > 1000 then
						CalculationResults.castsBase = "\226\136\158"
						CalculationResults.castsRegen = 0
						CalculationResults.DOOM = "\226\136\158"
						CalculationResults.SOOM = nil
					else
						CalculationResults.castsBase = base_casts
						CalculationResults.castsRegen = regen_casts
						CalculationResults.DOOM = math_floor(CalculationResults.DPM * (PlayerMana + regen_total) + 0.5)
					end
				end
			end
		end
		return calcAvgDmgCrit
	end
end

function DrDamage:CasterTooltip( frame, name, rank )
	self:CasterCalc(name, rank, true)
	
	local baseSpell
	if spellInfo[name]["Secondary"] and ((settings.SwapCalc and not IsShiftKeyDown()) or (IsShiftKeyDown() and not settings.SwapCalc)) then
		baseSpell = spellInfo[name]["Secondary"][0]
	else
		baseSpell = spellInfo[name][0]
		if type(baseSpell) == "function" then baseSpell, _, name = baseSpell() end
	end	
	
	frame:AddLine(" ")
	
	local r, g, b = 1, 0.82745098, 0
	
	if CalculationResults.Name then
		frame:AddLine( CalculationResults.Name, r, g, b )
		frame:AddLine(" ")	
	elseif name ~= frame:GetSpell() then
		frame:AddLine( name, r, g, b )
		frame:AddLine(" ")
	end

	if not settings.DefaultColor then
		r, g, b = 0, 0.7, 0
	end

	local healingSpell = CalculationResults.Healing
	local spellType, spellAbbr

	if healingSpell then
		spellType = L["Heal"]
		spellAbbr = L["H"]
	else
		spellType = L["Dmg"]
		spellAbbr = L["D"]
	end
	
	if settings.PlusDmg then
		local sp = CalculationResults.SpellDmgM and DrD_Round( CalculationResults.SpellDmg * CalculationResults.SpellDmgM * CalculationResults.DmgM, 1 )
		local ap = CalculationResults.SpellAPBonus and DrD_Round( CalculationResults.AP * CalculationResults.SpellAPBonus * CalculationResults.DmgM, 1 )
		if ap or sp then
			frame:AddDoubleLine(L["Effective"] .. " " .. (sp and L["SP"] or "") .. (sp and ap and "/" or "") .. (ap and L["AP"] or "") .. ":", (sp or "") .. (sp and ap and "/" or "") .. (ap or ""), 1, 1, 1, r, g, b  )
		end
	end

	if settings.Coeffs then
		local sp = CalculationResults.SpellDmgM
		local ap = CalculationResults.SpellAPBonus
		if ap or sp then
			frame:AddDoubleLine(L["Coeffs"] --[[.. (sp and ap and (" " .. L["SP"] .. "/"  .. L["AP"]) or "")--]] .. ":", (sp and (CalculationResults.SpellDmgM .."*" .. CalculationResults.SpellDmg) or "") .. (sp and ap and "/" or "") .. (ap and (CalculationResults.SpellAPBonus .. "*" .. CalculationResults.AP) or ""), 1, 1, 1, r, g, b  )
		end
		frame:AddDoubleLine(L["Multiplier:"], (CalculationResults.DmgM * 100) .. "%", 1, 1, 1, r, g, b  )
		if CalculationResults.CritM then
			frame:AddDoubleLine(L["Crit Multiplier:"], CalculationResults.CritM .. "%", 1, 1, 1, r, g, b  )
		end
	end

	if settings.DispCrit and CalculationResults.CritRate then
		frame:AddDoubleLine(L["Crit:"], CalculationResults.CritRate .. "%", 1, 1, 1, r, g, b )
	end

	if settings.DispHit and not baseSpell.Unresistable and not healingSpell then
		frame:AddDoubleLine(L["Hit:"], CalculationResults.HitRate .. "%", 1, 1, 1, r, g, b )
	end  				

	if not settings.DefaultColor then
		r, g, b = 0.8, 0.8, 0.9
	end

	if settings.AvgHit then
		frame:AddDoubleLine(L["Avg:"], CalculationResults.AvgHit .. " (".. CalculationResults.MinHit .."-".. CalculationResults.MaxHit ..")", 1, 1, 1, r, g, b )

		if CalculationResults.AvgLeech then
			frame:AddDoubleLine(L["Avg Heal:"], CalculationResults.AvgLeech, 1, 1, 1, r, g, b )
		end
	end

	if settings.AvgCrit and CalculationResults.AvgCrit and not baseSpell.sHits then
		frame:AddDoubleLine(L["Avg Crit:"], CalculationResults.AvgCrit .. " (".. CalculationResults.MinCrit .."-".. CalculationResults.MaxCrit ..")", 1, 1, 1, r, g, b )
	end
	
	if settings.Extra then
		if CalculationResults.Extra then
			if CalculationResults.ExtraAvg then
				frame:AddDoubleLine((CalculationResults.ExtraName or L["Additional"]) .. " " .. L["Avg/Proc:"], CalculationResults.ExtraAvg .. "/" .. CalculationResults.Extra, 1, 1, 1, r, g, b)
				if CalculationResults.ExtraMax then
					frame:AddDoubleLine((CalculationResults.ExtraName or L["Additional"]) ..  " " .. L["Max"] .. ":", CalculationResults.ExtraMax, 1, 1, 1, r, g, b)
				end
			else
				frame:AddDoubleLine((CalculationResults.ExtraName or L["Additional"]) .. ":", CalculationResults.Extra, 1, 1, 1, r, g, b)
			end
		end
	end		
	
	if settings.Ticks and CalculationResults.PerHit then
		frame:AddDoubleLine(L["Hits:"], CalculationResults.Hits .. "x ~" .. CalculationResults.PerHit, 1, 1, 1, r, g, b )

		if CalculationResults.PerCrit and CalculationResults.Crits > 0 then
			frame:AddDoubleLine(L["Crits:"], CalculationResults.Crits .. "x ~" .. CalculationResults.PerCrit, 1, 1, 1, r, g, b )
		end			
		if CalculationResults.PerHitHeal then
			frame:AddDoubleLine(L["Hits Heal:"], CalculationResults.Hits .. "x ~" .. CalculationResults.PerHitHeal, 1, 1, 1, r, g, b )
		end
		if baseSpell.DotStacks then
			frame:AddDoubleLine(L["Ticks:"] .. " (x" .. baseSpell.DotStacks .. ")", CalculationResults.Hits .. "x ~" .. (CalculationResults.PerHit * baseSpell.DotStacks), 1, 1, 1, r, g, b )
		end			
	end

	if settings.Extra then 
		if CalculationResults.ChainDmg then
			frame:AddDoubleLine(L["Avg Chain:"], CalculationResults.ChainDmg, 1, 1, 1, r, g, b  )
		end
		if CalculationResults.DotDmg > 0 then
			frame:AddDoubleLine(L["Dot"] .. ":", CalculationResults.DotDmg, 1, 1, 1, r, g, b )
		end
	end  				

	if settings.Total and CalculationResults.AvgTotal ~= CalculationResults.AvgHit then
		frame:AddDoubleLine(L["Avg Total:"], CalculationResults.AvgTotal, 1, 1, 1, r, g, b  )
	end

	if not settings.DefaultColor then
		r, g, b = 0.3, 0.6, 0.5
	end

	if not baseSpell.NoNext then
		local dmgA = CalculationResults.NextTenDmg and (CalculationResults.NextTenDmg > 0) and (CalculationResults.AvgTotal * 0.1) / CalculationResults.NextTenDmg
		local critA = CalculationResults.NextCrit and (CalculationResults.NextCrit > 0) and (CalculationResults.AvgTotal * 0.01 / CalculationResults.NextCrit * self:GetRating("Crit", nil, true ))
		local hasA = not baseSpell.eDot and not baseSpell.DotStacks and (CalculationResults.CastTime > 1) and (self:GetRating("SpellHaste",nil,true) + 0.01 * CalculationResults.Haste)
		local rating = CalculationResults.NextHit and (CalculationResults.NextHit > 0.5) and (baseSpell.MeleeHit and self:GetRating("MeleeHit", nil, true) or self:GetRating("Hit", nil, true))
		local hitA = rating and (CalculationResults.AvgTotal * 0.01 / CalculationResults.NextHit * rating)		

		if settings.Next then
			local dmgA, critA, hasA, hitA = dmgA and DrD_Round(dmgA, 1), critA and DrD_Round(critA, 1), hasA and DrD_Round(hasA, 1), hitA and DrD_Round(hitA, 1)
			if dmgA then frame:AddDoubleLine("+10 " .. L["SP"] .. ":", "+" .. CalculationResults.NextTenDmg, 1, 1, 1, r, g, b ) end
			if critA then frame:AddDoubleLine("+1% " .. L["Crit"] .. " (" .. self:GetRating("Crit") .. "):", "+" .. CalculationResults.NextCrit, 1, 1, 1, r, g, b ) end
			if hitA then frame:AddDoubleLine("+1% " ..  L["Hit"] .. " (" .. DrD_Round(rating,2).. "):", "+" .. CalculationResults.NextHit, 1, 1, 1, r, g, b ) end
			frame:AddDoubleLine("+1% " .. spellAbbr .. (CalculationResults.SpamDPS and L["PSC"] or L["PS"]) .. " (" .. ((critA and (L["Cr"] .. "|")) or "") .. ((hitA and (L["Ht"] .. "|")) or "") .. ((hasA and (L["Ha"] .. "|")) or "") .. L["SP"] .. "):", (critA and (critA .. "/") or "") .. (hitA and (hitA .. "/") or "") .. (hasA and (hasA .. "/") or "") .. (dmgA or "-"), 1, 1, 1, r, g, b )
		end
		if settings.CompareDmg and (hitA or critA or hasA) and dmgA then
			local critA = critA and DrD_Round(critA / dmgA, 2)
			local hitA = hitA and DrD_Round(hitA / dmgA, 2)
			local hasA = hasA and DrD_Round(hasA / dmgA, 2)
			frame:AddDoubleLine("+1 " .. L["SP"] .. " (" .. (critA and L["Cr"] or "") .. (critA and hitA and "|" or "") .. (hitA and L["Ht"] or "") .. (hasA and (critA or hitA) and "|" or "") .. (hasA and L["Ha"] or "") .. "):", (critA or "") .. (critA and hitA and "/" or "") .. (hitA or "") .. ((critA or hitA) and hasA and "/" or "") .. (hasA or ""), 1, 1, 1, r, g, b )
		end
		if settings.CompareCrit and critA then
			local dmgA = dmgA and DrD_Round(dmgA / critA, 2) or "-"
			local hitA = hitA and DrD_Round(hitA / critA, 2)
			local hasA = hasA and DrD_Round(hasA / critA, 2)
			frame:AddDoubleLine("+1 " .. L["Cr"] .. " (" .. L["SP"] .. (hitA and ("|" .. L["Ht"]) or "") .. (hasA and ("|" .. L["Ha"]) or "") .. "):", dmgA .. (hitA and ("/" .. hitA) or "") .. (hasA and ("/" .. hasA) or ""), 1, 1, 1, r, g, b )
		end
		if settings.CompareHit and hitA then
			local dmgA = dmgA and DrD_Round(dmgA / hitA, 2) or "-"
			local critA = critA and DrD_Round(critA / hitA, 2)
			local hasA = hasA and DrD_Round(hasA / hitA, 2)
			frame:AddDoubleLine("+1 " .. L["Ht"] .. " (" .. L["SP"] .. (critA and ("|" .. L["Cr"]) or "") .. (hasA and ("|" .. L["Ha"]) or "") .. "):", dmgA .. (critA and ("/" .. critA) or "") .. (hasA and ("/" .. hasA) or ""), 1, 1, 1, r, g, b )
		end
		if settings.CompareHaste and hasA then
			local dmgA = dmgA and DrD_Round(dmgA / hasA, 2) or "-"
			local critA = critA and DrD_Round(critA / hasA, 2)
			local hitA = hitA and DrD_Round(hitA / hasA, 2)
			frame:AddDoubleLine("+1 " .. L["Ha"] .. " (" .. L["SP"] .. (critA and ("|" .. L["Cr"]) or "") .. (hitA and ("|" .. L["Ht"]) or "") .. "):", dmgA .. (critA and ("/" .. critA) or "") .. (hitA and ("/" .. hitA) or ""), 1, 1, 1, r, g, b )
		end
	end

	if not settings.DefaultColor then
		r, g, b = 0.8, 0.1, 0.1
	end

	if settings.DPS and not baseSpell.NoDPS then
		if CalculationResults.DPSC ~= CalculationResults.DPS then
			frame:AddDoubleLine(spellAbbr .. L["PS"] .. "/" .. spellAbbr .. L["PSC"] .. ":", CalculationResults.DPS .. ((CalculationResults.ExtraDPS and ("+" .. CalculationResults.ExtraDPS)) or "") .. "/" .. CalculationResults.DPSC, 1, 1, 1, r, g, b)
		else
			frame:AddDoubleLine(spellAbbr .. L["PS"] .. ":", CalculationResults.DPS .. ((CalculationResults.ExtraDPS and ("+" .. CalculationResults.ExtraDPS)) or "") , 1, 1, 1, r, g, b)
		end
		if CalculationResults.SpamDPS then
			frame:AddDoubleLine(spellAbbr .. L["PS (spam):"], CalculationResults.SpamDPS, 1, 1, 1, r, g, b)
		end
		if CalculationResults.DPSCD and CalculationResults.Cooldown then
			frame:AddDoubleLine(spellAbbr .. L["PS (CD):"], CalculationResults.DPSCD, 1, 1, 1, r, g, b)
		end
		
	end

	if settings.DPM and CalculationResults.DPM and not baseSpell.NoDPM then
		frame:AddDoubleLine(spellAbbr .. L["PM:"], CalculationResults.DPM, 1, 1, 1, r, g, b )
		if CalculationResults.SpamDPM and CalculationResults.SpamDPM ~= CalculationResults.DPM then
			frame:AddDoubleLine(spellAbbr .. L["PM (spam):"], CalculationResults.SpamDPM, 1, 1, 1, r, g, b )
		end
	end

	if settings.Doom and CalculationResults.DOOM and not baseSpell.NoDoom then
		frame:AddDoubleLine(spellType .. " " .. L["until OOM:"], CalculationResults.DOOM, 1, 1, 1, r, g, b )
	end

	if settings.Casts and CalculationResults.castsBase then
		frame:AddDoubleLine(((CalculationResults.castsRegen > 0) and L["Casts (rgn):"]) or L["Casts:"], CalculationResults.castsBase .. ((CalculationResults.castsRegen > 0 and ("+" .. CalculationResults.castsRegen)) or "") .. ((CalculationResults.SOOM and (" (" .. CalculationResults.SOOM .. "s)")) or ""), 1, 1, 1, r, g, b )
	end

	if settings.ManaUsage then
		if CalculationResults.TrueManaCost and CalculationResults.TrueManaCost ~= CalculationResults.ManaCost then
			frame:AddDoubleLine(L["True Mana Cost:"], CalculationResults.TrueManaCost, 1, 1, 1, r, g, b)
		end
		if CalculationResults.MPS then
			frame:AddDoubleLine(L["MPS"] .. ":", CalculationResults.MPS, 1, 1, 1, r, g, b)
		end
	end
	
	if spellInfo[name]["Secondary"] then
		frame:AddLine(" ")
		frame:AddLine(L["Hint: Hold Shift for secondary tooltip"], 0, 1, 0)
	end
	frame:Show()
end